package com.sword.ehcache.config;

import com.sword.ehcache.common.CommonUtil;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.util.PropertyUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.cache.CacheProperties;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.ehcache.EhCacheManagerUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.Resource;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Properties;

/**
 * 缓存配置
 * @author sword
 * @date 2020/9/30 17:26
 */
@Configuration
@EnableCaching
public class CacheConfig {

    /**
     * rmi方式的节点监听器类型
     */
    public static final String RMI_CACHE_MANAGER_PEER_LISTENER_FACTORY_CLASS_NAME =
            "net.sf.ehcache.distribution.RMICacheManagerPeerListenerFactory";

    /**
     * 主机名称参数键名
     */
    public static final String HOST_NAME = "hostName";

    /**
     * 监听其他节点发送到当前CacheManager的信息的监听器端口
     */
    @Value("${spring.cache.ehcache.manager-peer-listener-port}")
    private String cacheManagerPeerListenerPort;

    /**
     * 监听其他节点发送到当前CacheManager的信息的监听器端口参数键名
     */
    public static final String CACHE_MANAGER_PEER_LISTENER_PORT_NAME = "port";

    /**
     * 如果缓存用的是EhCache则需要自定义CacheManager
     * 因为在设置cacheManagerPeerListener时，如果hostName为空则默认使用InetAddress.getLocalHost().getHostAddress()
     * 来获取服务器的IP，但Linux服务器用这个方法只能获取到127.0.0.1，所以在hostName为空时需要换种方法获取IP
     * @param cacheProperties 缓存配置
     * @return net.sf.ehcache.CacheManager
     * @author sword
     * @date 2020/10/9 14:49
     */
    @Bean
    @Primary
    @ConditionalOnProperty(name = "spring.cache.type", havingValue = "ehcache")
    public CacheManager ehCacheCacheManager(CacheProperties cacheProperties) {
        // 配置文件，文件路径对应spring.cache.ehcache.config，默认为classpath:ehcache.xml
        Resource location = cacheProperties.resolveConfigLocation(cacheProperties.getEhcache().getConfig());

        // 如果配置文件不为空则根据配置文件生成CacheManager，否则返回默认CacheManager
        if (location != null) {
            // 解析配置文件生存配置参数对象
            net.sf.ehcache.config.Configuration configuration = EhCacheManagerUtils.parseConfiguration(location);

            // 计算本机ip地址
            calculateHostAddress(configuration);

            return new CacheManager(configuration);
        }
        else {
            return EhCacheManagerUtils.buildCacheManager();
        }
    }

    /**
     * 计算本机ip地址
     * @param configuration EhCache配置参数对象
     * @author sword
     * @date 2020/10/10 9:15
     */
    public void calculateHostAddress(net.sf.ehcache.config.Configuration configuration) {
        configuration.getCacheManagerPeerListenerFactoryConfigurations().forEach(factoryConfiguration -> {
            // 如果不是RMI方式的节点监听器则直接跳过
            if (!RMI_CACHE_MANAGER_PEER_LISTENER_FACTORY_CLASS_NAME.equals(factoryConfiguration.getFullyQualifiedClassPath())) {
                return;
            }

            // cacheManagerPeerListener的配置参数
            Properties properties = PropertyUtil.parseProperties(
                    factoryConfiguration.getProperties(), factoryConfiguration.getPropertySeparator()
            );

            // 如果主机名称不为空则直接返回
            if (StringUtils.isNotEmpty(properties.getProperty(HOST_NAME))) {
                return;
            }

            // 设置主机名称，即本机的Ipv4地址
            properties.setProperty(HOST_NAME, CommonUtil.getHostAddressIpv4());

            // 顺便设置一下监听其他节点发送到当前CacheManager的信息的监听器端口
            properties.setProperty(CACHE_MANAGER_PEER_LISTENER_PORT_NAME, cacheManagerPeerListenerPort);

            // 将修改后的配置参数重新设置回去
            try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
                properties.store(outputStream, null);
                factoryConfiguration.setProperties(outputStream.toString());
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
    }


}
